//
//  OpaqueTypes.swift
//  cnswift-demo
//
//  Created by wyang on 2022/8/24.
//  Copyright © 2022 YangWei. All rights reserved.
//

import Foundation

class OpaqueTypes: SWDBaseObject {
    
    class func run() {
        
        let opaqueTypes = OpaqueTypes()
        
        // 1 不透明类型解决的问题
        opaqueTypes.theProblemThatOpaqueTypesSolve()
        
        // 2 返回不透明类型
        opaqueTypes.returningAnOpaqueType()
        
        // 3 不透明类型和协议类型的区别
        opaqueTypes.differencesBetweenOpaqueTypesAndProtocolTypes()
    }
    
    /// 1 不透明类型解决的问题
    func theProblemThatOpaqueTypesSolve() {
        let title = "1 不透明类型解决的问题";
        printStartLine(title)
        
        let smallTriangle = Triangle(size: 3)
        print(smallTriangle.draw())
        // *
        // **
        // ***
        
        // 额外对使用者曝露了一个 FlippedShape 类型
        let flippedTriangle = FlippedShape(shape: smallTriangle)
        print(flippedTriangle.draw())
        // ***
        // **
        // *
        
        // 拼接一个翻转三角形和一个普通三角形
        // 会得到一个类似于 JoinedShape<FlippedShape<Triangle>, Triangle> 这样的类型
        let joinedTriangles = JoinedShape(top: smallTriangle, bottom: flippedTriangle)
        print(joinedTriangles.draw())
        // *
        // **
        // ***
        // ***
        // **
        // *
        
        printEndLine(title)
    }
    
    /// 1 不透明类型解决的问题
    /// 三角形
    struct Triangle: Shape {
        var size: Int
        func draw() -> String {
            var result: [String] = []
            for length in 1...size {
                result.append(String(repeating: "*", count: length))
            }
            return result.joined(separator: "\n")
        }
    }
    
    /// 1 不透明类型解决的问题
    /// 2 返回不透明类型
    /// 利用泛型来实现垂直翻转之类的操作
    /// 存在局限：翻转操作的结果会暴露我们用于构造结果的泛型类型
    struct FlippedShape<T: Shape>: Shape {
        var shape: T
        func draw() -> String {
            // 修正 invalidFlip(_:)
            if shape is Square {
                return shape.draw()
            }
            let lines = shape.draw().split(separator: "\n")
            return lines.reversed().joined(separator: "\n")
        }
    }
    
    /// 1 不透明类型解决的问题
    /// 将几何图形垂直拼接起来
    struct JoinedShape<T: Shape, U: Shape>: Shape {
        var top: T
        var bottom: U
        func draw() -> String {
            return top.draw() + "\n" + bottom.draw()
        }
    }
    
    /// 2 返回不透明类型
    func returningAnOpaqueType() {
        let title = "2 返回不透明类型";
        printStartLine(title)
        
        /* 返回一个梯形 */
        let trapezoid = makeTrapezoid()
        print(trapezoid.draw())
        // *
        // **
        // **
        // **
        // **
        // *
        
        /* 将不透明返回类型和泛型结合起来 */
        let smallTriangle = Triangle(size: 3)
        let opaqueJoinedTriangles = join(smallTriangle, flip(smallTriangle))
        print(opaqueJoinedTriangles.draw())
        // *
        // **
        // ***
        // ***
        // **
        // *
        
        /* 不正确示例 —— 包含针对 Square 类型进行特殊处理的翻转函数 */
        /* 修正前 */
        /*
        func invalidFlip<T: Shape>(_ shape: T) -> some Shape {
            if shape is Square {
                return shape // 错误：返回类型不一致
            }
            return FlippedShape(shape: shape) // 错误：返回类型不一致
        }
         */
        /* 修正后 */
        // 将针对 Square 的特殊处理移入到 FlippedShape 的实现中去
        func invalidFlip<T: Shape>(_ shape: T) -> some Shape {
            return FlippedShape(shape: shape) // 错误：返回类型不一致
        }
        
        /* 在返回的底层类型中使用泛型参数 */
        let t = `repeat`(shape: smallTriangle, count: 5)
        print(t)
        
        printEndLine(title)
    }
    
    /// 2 返回不透明类型
    /// 正方形
    struct Square: Shape {
        var size: Int
        func draw() -> String {
            let line = String(repeating: "*", count: size)
            let result = Array<String>(repeating: line, count: size)
            return result.joined(separator: "\n")
        }
    }

    /// 2 返回不透明类型
    /// 梯形
    /// 可以返回任意需遵循 Shape 协议的类型
    func makeTrapezoid() -> some Shape {
        let top = Triangle(size: 2)
        let middle = Square(size: 2)
        let bottom = FlippedShape(shape: top)
        let trapezoid = JoinedShape(
            top: top,
            bottom: JoinedShape(top: middle, bottom: bottom)
        )
        return trapezoid
    }
    
    /// 2 返回不透明类型
    /// 将不透明返回类型和泛型结合起来
    func flip<T: Shape>(_ shape: T) -> some Shape {
        return FlippedShape(shape: shape)
    }
    
    /// 2 返回不透明类型
    /// 将不透明返回类型和泛型结合起来
    func join<T: Shape, U: Shape>(_ top: T, _ bottom: U) -> some Shape {
        JoinedShape(top: top, bottom: bottom)
    }
    
    /// 2 返回不透明类型
    /// 在返回的底层类型中使用泛型参数
    func `repeat`<T: Shape>(shape: T, count: Int) -> some Collection {
        return Array<T>(repeating: shape, count: count)
    }
    
    /// 3 不透明类型和协议类型的区别
    func differencesBetweenOpaqueTypesAndProtocolTypes() {
        let title = "3 不透明类型和协议类型的区别";
        printStartLine(title)
        
        /* 使用协议类型代替不透明类型：函数的返回结果不能用 == 运算符进行比较了 */
        let smallTriangle = Triangle(size: 3)
        
        let protoFlippedTriangle = protoFlip(smallTriangle)
        let sameThing = protoFlip(smallTriangle)
//        protoFlippedTriangle == sameThing  // 错误
        
        
        /* 经过翻转操作后的结果类型并不能作为 protoFlip(_:) 的形参 */
//        protoFlip(protoFlip(smallTriangle))
        
        // 分步解析对比
        let typeA1 = protoFlip(smallTriangle);
        print("protoFlip(smallTriangle) type = ", type(of:typeA1))
//        let typeA2 = protoFlip(typeA1);
//        print(typeA2)
        
        /* 可以嵌套：因为返回不透明类型 some Shape 是一个确定遵循协议类型的 struct/enum/class type */
        flip(flip(smallTriangle))
        
        // 分步解析对比
        let typeB1 = flip(smallTriangle);
        print("flip(smallTriangle) type = ", type(of:typeB1))
        let typeB2 = flip(typeB1);
        print("flip(flip(smallTriangle)) = ", type(of:typeB2))
        
        // typeA1 typeB1 typeB2 好像类型是一样的 🤔️ ❓
        // TODO：
        // 为什么 flip(flip(smallTriangle)) 里面没有关联类型，也必须加 some，OC里面只要满足是协议就可以循环嵌套
        // 推测应该是语法问题，待验证
        
        // 编译报错
//        if Shape is Shape {
//            print("Shape is Shape")
//        }
        
        if typeB1 is Shape {
            print("typeB1 is Shape")
        }
        
        
        /* 错误：有关联类型的协议不能作为返回类型。*/
        /*
        func makeProtocolContainer<T>(item: T) -> Container {
            return [item]
        }
         */
        
        /* 错误：没有足够多的信息来推断 C 的类型。*/
        /*
        func makeProtocolContainer<T, C: Container>(item: T) -> C {
            return [item]
        }
        */
        
        // 使用不透明类型 some Container 作为返回类型
        func makeOpaqueContainer<T>(item: T) -> some Container {
            return [item]
        }
        
        let opaqueContainer = makeOpaqueContainer(item: 12)
        let twelve = opaqueContainer[0]
        print(twelve)
        print(type(of: twelve))
        
        // 使用 12.0，type(of: twelve)为 Double
        
         
        printEndLine(title)
    }
    
    /// 3 不透明类型和协议类型的区别
    /// 对比 func flip<T: Shape>(_ shape: T) -> some Shape
    func protoFlip<T: Shape>(_ shape: T) -> Shape {
        // 可以返回多种类型了
        if shape is Square {
            return shape
        }
        return FlippedShape(shape: shape)
    }
}

/// 1 不透明类型解决的问题
/// 绘制 ASCII 符号构成的几何图形
protocol Shape {
    // 有一个 draw() 方法：返回一个代表最终几何图形的字符串
    func draw() -> String
}

